package gmu.swe.project.tracker;

import gmu.swe.project.peer.PeerInfo;
import gmu.swe.project.query.Query;
import gmu.swe.project.query.QueryResult;
import gmu.swe.project.query.QueryResultSet;
import gmu.swe.project.utils.Helper;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;

/**
 * Thread supports the client registration socket for the Tracker.
 * @author Christina
 *
 */
public class TrackerServiceThread extends Thread {
	private Socket registrationSocket = null;
	private ObjectInputStream ois = null;
	private ObjectOutputStream oos = null;
	private int curWorkload = 0;

    public TrackerServiceThread(Socket socket) {
		super("TrackerServiceThread");
		this.registrationSocket = socket;
//		System.out.println("Spawned thread for client registration and query on Tracker "+socket);
    }
    
    public void run() {

		try {	
			
			//connect an input stream to it to get data from the Peer
			ois = new ObjectInputStream(registrationSocket.getInputStream());
			//connect a reader on it to send data to the Peer
			oos = new ObjectOutputStream(registrationSocket.getOutputStream());
			//read an object from the stream
			Object obj = ois.readObject();
			
			//if this is a Peer registering, add it's PeerInfo to the PeerRegistration
			if(obj instanceof PeerInfo) {
				//get the info from the Peer
				PeerInfo peerInfo = (PeerInfo) obj;
				//add the data to the PeerRegistration; if this Peer already exists in the set
				//the current info will be replaced with the new info
				Tracker.updatePeerRegistration(peerInfo);
				//get the ID of the message and include in ACK
				String id = peerInfo.getId();
				//send back an acknowledgment of receipt
				oos.writeObject("ACK: " + id + ";");
			//If this is a Query, handle it as is appropriate from a Peer or a Tracker.	
			} else if(obj instanceof Query) {
				Query query = (Query) obj;
				//handle the query
				QueryResultSet queryResultSet = handleQuery(query);
				oos.writeObject(queryResultSet);
			}
		} catch(ClassNotFoundException cnfex) {
			//do something
		} catch(UnknownHostException uhex) {
			//do something
		} catch(IOException ioex) {
			//do something
		} finally {
			try {
				//close the input stream
				ois.close();	
			} catch(Exception ex) {}
			try {
				//close the output stream
				oos.close();	
			} catch(Exception ex) {}
			try {
				//close the sockets
				registrationSocket.close();	
			} catch(Exception ex) {}
		}
    	
    }

    
	private static QueryResultSet handleQuery(Query query) {
		//need to check the type of query
		String type = query.getType();
		
		//add any results of peers connected to this tracker
		QueryResultSet queryResultSet = new QueryResultSet();

		//If this is a request from another Tracker, send just this Trackers results
		if(type.equals(Query.TRACKER_QUERY)) {
			queryResultSet = searchPeers(query);
		} else if(type.equals(Query.PEER_QUERY)) {
			//ask other Trackers
			queryResultSet.addQueryResultSet(searchTrackers(query));
		}
		return queryResultSet;
	}

	private static QueryResultSet searchPeers(Query query) {
		QueryResultSet set = new QueryResultSet();
		QueryResult result;
		Iterator<PeerInfo> i = Tracker.peerRegistration.iterator();
		while(i.hasNext()) {
			//get a PeerInfo
			PeerInfo peer = (PeerInfo) i.next();
			//Get a listing of this Peer's files
			ArrayList<String> files = peer.getFiles();
			Iterator<String> j = files.iterator();
			while(j.hasNext()) {
				String file = (String)j.next();
				//if the query matches any part of the file's name, add it to the results
				if(file.indexOf(query.getQuery()) != -1) {
					result = new QueryResult();
					result.setFileName(file);
					result.setPeer(peer.getIpAddress());
					result.setPort(peer.getPort());
					result.setWorkload(peer.getWorkload());
					set.addQueryResult(result);
				}
			}
		}
		return set;
	}
	
	/**
	 * For each Tracker listed in the TrackerRegistration returned by the Registry, open
	 * a new socket and forward the query.  Add any results returned to the existing 
	 * QueryResultSet.
	 * 
	 * @param Query
	 * @return QueryResultSet
	 */
	private static QueryResultSet searchTrackers(Query query) {
		Socket qSocket = null;
		ObjectInputStream ois = null;
		ObjectOutputStream oos = null;
		
		QueryResultSet trackerSets = new QueryResultSet();
		
		Iterator<TrackerInfo> i = Tracker.trackerRegistration.iterator();
		while(i.hasNext()) {
			TrackerInfo trackerInfo = (TrackerInfo) i.next();
			try {			
					//create new socket
					qSocket = new Socket(trackerInfo.getIpAddress(),trackerInfo.getListeningPort());
					//connect a writer on it to send the query to the Tracker
					oos = new ObjectOutputStream(qSocket.getOutputStream());
					query.setType(Query.TRACKER_QUERY);
					oos.writeObject(query);
					
					//connect an input stream to it to get results from the Tracker
					ois = new ObjectInputStream(qSocket.getInputStream());
					QueryResultSet set = (QueryResultSet)ois.readObject();
					
					//Add the results to the set
					trackerSets.addQueryResultSet(set);
			} catch(IOException ioex) {
				//do something
			} catch(ClassNotFoundException cnfex) {
				//do something
			} finally {
				//close the reader, writer and socket.
				try {
					ois.close();
				} catch(Exception ex) {}
				try {
					oos.close();
				} catch(Exception ex) {}
				try {
					qSocket.close();	
				} catch(Exception ex) {}
			}
		}
		return trackerSets;
	}
}
